/*
    The MIT License

    Copyright (c) 2010 IFMO/GameDev Studio

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    THE SOFTWARE.
*/

#include "core.h"
#include <crtdbg.h>
#include <Windows.h>

//#define USE_LOG_FILE

/*-----------------------------------------------------------------------------
	Log :
-----------------------------------------------------------------------------*/	

struct ELogLine_s {
		ELogMsgType_t	msg_type;
		char			message[LOG_LINE_LEN];
		const char	*	src_file;
		uint			src_line;
		uint			result;
	};


const uint LOG_MAX_DEBUG_STRINGS = 256;

struct ELogDbgString_s {
		char	message[LOG_LINE_LEN];
	};

class ELog : public ILog {
	public:
							ELog			( void );
							~ELog			( void );
						
		virtual	uint		LogMessage			( ELogMsgType_t msg_type, const char *src_file, uint src_line, const char *frmt, ... );
		
		virtual void		SetWriteCB			( ILogCB *log_cb );
		virtual void		GetLogMessage		( uint line, ELogMessage_s &message ) const;
		virtual void		GetLogLastMessage	( ELogMessage_s &message ) const;
		virtual uint		GetLogMessageNum	( void ) const;
		virtual void		Clear				( void );
		virtual void		LogSplit			( const char *headline, char fill_char );

		virtual void		AddDebugString		( const char *frmt, ... );
		virtual void		ClearDebugStrings	( void );
		virtual const char	*GetDebugString		( uint index ) const;
		virtual uint		GetDebugStringNum	( void ) const;
		
	protected:
		EString			stupid_thing_to_initialize_atom_table_first;
		
		uint LogMessageLine		( ELogMsgType_t msg_type, const char *src_file, uint src_line, const char *text_line );
	
		ILogCB			*log_cb;
		
		uint			debug_strings_num;
		ELogDbgString_s	debug_strings[LOG_MAX_DEBUG_STRINGS];
		
		ELogLine_s	log_lines[LOG_MAX_LINES];
		uint		current_line;
		uint		total_lines;
		
		FILE		*log_file;
	};


/*-----------------------------------------------------------------------------
	Log export stuff :
-----------------------------------------------------------------------------*/	

ELog log_static_imp;
	
ILog *Log(void) 
{
	return &log_static_imp;
}


/*-----------------------------------------------------------------------------
	Log implementation :
-----------------------------------------------------------------------------*/	

//
//	DecolorizeString
//	TODO : move to 'library'.
//
void DecolorizeString(const char *input, char *output) 
{
	for ( ; *input; *input++, *output++ ) {
		if (*input=='^') {
			input++;
			if (*input>='0' && *input<='9') {
				input++;
			}
		}
		*output = *input;
	}
	*output = '\0';
}


//
//	ELog::ELog
//
ELog::ELog(void)
{
	OutputDebugStr(va("%s\r\n", __FUNCTION__));

	SetWriteCB(NULL);
	Clear();
	
	log_file	=	NULL;
#ifdef USE_LOG_FILE	
	log_file	=	fopen("engine.log", "a");
	if (log_file) {
		fputs("\r\n-------------------------------------------------------------------------------\r\n", log_file);
	}
#endif	
}


//
//	ELog::~ELog
//
ELog::~ELog(void)
{
	OutputDebugStr(va("%s\r\n", __FUNCTION__));

	if (log_file) {
		fclose(log_file);
	}
}


//
//	ELog::Clear
//
void ELog::Clear(void) 
{
	memset(log_lines,	0, sizeof(log_lines));
	current_line = 0;
	total_lines = 0;
}


systemTime_t SYS_GetLocalTime(void);


//
//	ELog::WriteLine
//	
uint ELog::LogMessage( ELogMsgType_t msg_type, const char *src_file, uint src_line, const char *frmt, ... )
{
	const uint MAX_SIZE = 4096;

	//	format message :
	char message[MAX_SIZE];
	va_list	argptr;
	va_start (argptr, frmt);
	vsnprintf (message, MAX_SIZE-1, frmt, argptr);
	va_end (argptr);
	
	//	split message onto lines :
	char line[MAX_SIZE];
	uint len = strlen(message);
	
	for (uint j=0, i=0; i<len+1; i++) {	
	
		char ch = message[i];

		//	skip '\r' :		
		if (ch=='\r') {
			continue;
		}

		//	new line if '\n' or line is too long :
		if (ch=='\n' || j>=LOG_LINE_LEN-1 || ch=='\0') {
			line[j] = '\0';
			j = 0;
			LogMessageLine( msg_type, src_file, src_line, line );
			continue;
		}
		
		//	just copy character :
		line[j] = ch;
		j++;
	}
	
	return 0;
}


//
//	ELog::LogMessageLine
//
uint ELog::LogMessageLine( ELogMsgType_t msg_type, const char *src_file, uint src_line, const char *message )
{
#ifndef _DEBUG
	if (msg_type==LOG_MSG_DEBUG) {
		return 0;
	}
#endif
	//	write message :
	uint len = strlen(message);
	
	if (len>LOG_LINE_LEN-1) {
		SIGNAL(__FUNCTION__"() message too long");
	}
	
	memset( log_lines[ current_line ].message, 0, LOG_LINE_LEN );
	memcpy( log_lines[ current_line ].message, message, min(len, LOG_LINE_LEN-1) );
	log_lines[ current_line ].msg_type	=	msg_type;
	log_lines[ current_line ].src_file	=	src_file;
	log_lines[ current_line ].src_line	=	src_line;
	
	current_line++;
	current_line %= LOG_MAX_LINES;
	total_lines++;
	
	//	call callback :
	if (log_cb) {
		ELogMessage_s	msg;
		GetLogLastMessage(msg);
		log_cb->Callback(msg);
	}
	
#if 1
	//	get time :
	systemTime_t t = SYS_GetLocalTime();
	uint h  = t.hour;
	uint m  = t.minute;
	uint s  = t.sec;
	uint ms = t.msec;

	#ifdef USE_LOG_FILE
		
		const char *prefix	=	NULL;
		if (msg_type==LOG_MSG_ERROR)	prefix	=	"[ ERROR ] "; else
		if (msg_type==LOG_MSG_WARNING)	prefix	=	"[WARNING] "; else
		if (msg_type==LOG_MSG_FATAL)	prefix	=	"[ FATAL ] "; else
		if (msg_type==LOG_MSG_DEBUG)	prefix	=	"[ DEBUG ] "; else
		if (msg_type==LOG_MSG_SCRIPT)	prefix	=	"          "; else
		if (msg_type==LOG_MSG_DEBUG)	prefix	=	"          "; else
										prefix	=	"          ";
		if (log_file) {
			fputs(va("[%02d:%02d:%02d.%03d] : ", h, m, s, ms), log_file);
			fputs(prefix, log_file);
			fputs(message, log_file);
			fputs("\n", log_file);
			fflush(log_file);
		}
	#endif
	
	OutputDebugString(va("    [%02d:%02d:%02d.%03d] %s\r\n", h, m, s, ms, message));
#endif
	
	return 0;
}


//
//	ELog::SetWriteCB
//						  
void ELog::SetWriteCB( ILogCB *log_cb )
{
	//	set callback :
	this->log_cb	=	log_cb;
	
	//	call callback for all old messages :
	if (log_cb) {
		uint num = GetLogMessageNum();
		for (int i=(int)num; i>=0; i--) {
			ELogMessage_s	msg;
			
			Log()->GetLogMessage(i, msg);
			log_cb->Callback( msg );
		}
	}
}


//
//	ELog::GetLogMessage
//
void ELog::GetLogMessage( uint line, ELogMessage_s &message ) const
{
	if (line>=GetLogMessageNum()) {
		strcpy(message.message, "");
		message.src_file	=	"";
		message.src_line	=	0;
		message.msg_type	=	LOG_MSG_INFO;
		return;
	}

	uint n = (total_lines - line - 1) % LOG_MAX_LINES;

	memcpy( &message, &log_lines[ n ], sizeof(ELogMessage_s) );
}


//
//	ELog::GetLogLastMessage
//
void ELog::GetLogLastMessage( ELogMessage_s &message ) const
{
	memcpy( &message, &log_lines[ current_line-1 ], sizeof(ELogMessage_s) );
}


//
//	ELog::GetLineNum
//
uint ELog::GetLogMessageNum( void ) const
{
	return min(total_lines, LOG_MAX_LINES);
}


//
//	ELog::LogSplit
//
void ELog::LogSplit( const char *_headline, char fill_char )
{
	//	68 chars
	char LINE[] = "----------------------------------------------------------------------";
	memset(LINE, fill_char, sizeof(LINE)-1);

	if (_headline[0]=='\0') {
		LogMessage(LOG_MSG_INFO, __FILE__,__LINE__, LINE);
		LogMessage(LOG_MSG_INFO, __FILE__,__LINE__, "");
		return;
	}

	
	std::string	headline	=	_headline;
	
	if (headline.length()>55) {
		headline.resize(55);
		headline	+=	"...";
	}
	
	headline	=	std::string("---- ") + headline + std::string(LINE);
	headline.resize(70);
	
	LogMessage(LOG_MSG_INFO, __FILE__,__LINE__, "");
	LogMessage(LOG_MSG_INFO, __FILE__,__LINE__, "%s", headline.c_str());
}


/*-----------------------------------------------------------------------------
	Debug strings :	
-----------------------------------------------------------------------------*/

//
//	ELog::AddDebugString
//
void ELog::AddDebugString( const char *frmt, ... )
{
	if (debug_strings_num>=LOG_MAX_DEBUG_STRINGS) {
		return;
	}
	
	uint i = debug_strings_num;
	debug_strings_num++;

	va_list	argptr;
	va_start (argptr, frmt);
	vsnprintf (debug_strings[i].message, LOG_LINE_LEN-1, frmt, argptr);
	va_end (argptr);
}


//
//	ELog::ClearDebugStrings
//
void ELog::ClearDebugStrings( void )
{
	debug_strings_num	=	NULL;
	for (uint i=0; i<LOG_MAX_DEBUG_STRINGS; i++) {
		strcpy(debug_strings[i].message, "");
	}
}


//
//	ELog::GetDebugString
//
const char	* ELog::GetDebugString( uint index ) const
{
	if (index>=debug_strings_num) {
		return NULL;
	}

	return debug_strings[index].message;
}


//
//	ELog::GetDebugStringNum
//
uint ELog::GetDebugStringNum( void ) const
{
	return debug_strings_num;
}












